import got from 'got';
import stream from 'stream';
import {existsSync, mkdirSync, writeFileSync, createWriteStream, readFileSync, statSync, unlinkSync} from 'fs';
import {promisify} from 'util';
import {dirname} from 'path';
import {getTrackDownloadUrl, decryptDownload, addTrackTags} from 'd-fi-core';
import logUpdate from 'log-update';
import chalk from 'chalk';
import conf from './config';
import signale from '../lib/signale';
import {saveLayout, getFileSize, progressBar} from './util';
import type {trackType} from 'd-fi-core/dist/types';

const pipeline = promisify(stream.pipeline);
const simulate = process.env.SIMULATE;

interface downloadTrackProps {
  track: trackType;
  quality: string | number;
  info: {[key: string]: any};
  path: string;
  totalTracks: number;
  trackNumber?: boolean;
  fallbackTrack?: boolean;
  isFallback?: boolean;
  message?: string;
}

const downloadTrack = async ({
  track,
  quality,
  path,
  info,
  totalTracks,
  trackNumber = true,
  fallbackTrack = true,
  isFallback = false,
  message = '',
}: downloadTrackProps): Promise<string | undefined> => {
  logUpdate(signale.pending(track.SNG_TITLE + ' by ' + track.ART_NAME + ' from ' + track.ALB_TITLE));
  try {
    let ext = '.mp3',
      fileSize = 0,
      downloaded = 0,
      coverSize = 500;
    switch (quality) {
      case 1:
      case '1':
      case '128':
      case 'MP3_128':
      case '128kbps':
        quality = 1;
        fileSize = Number(track.FILESIZE_MP3_128);
        coverSize = Number(conf.get('coverSize.128', 500));
        break;
      case 9:
      case '9':
      case 'flac':
      case 'Flac':
      case 'FLAC':
        quality = 9;
        ext = '.flac';
        fileSize = Number(track.FILESIZE_FLAC);
        coverSize = Number(conf.get('coverSize.flac', 1000));
        break;
      default:
        quality = 3;
        fileSize = Number(track.FILESIZE_MP3_320);
        coverSize = Number(conf.get('coverSize.320', 500));
    }

    const savePath =
      saveLayout({track, album: info, path, trackNumber, minimumIntegerDigits: totalTracks >= 100 ? 3 : 2}) + ext;
    if (existsSync(savePath)) {
      logUpdate(signale.info(`Skipped "${track.SNG_TITLE}", track already exists.`));
      logUpdate.done();
      logUpdate(signale.note(savePath));
      logUpdate.done();
      return savePath;
    }

    const url = getTrackDownloadUrl(track, quality);
    fileSize = await getFileSize(url, 0);
    if (fileSize < 1) {
      if (fallbackTrack && track.FALLBACK && !isFallback && track.ART_ID === track.FALLBACK.ART_ID) {
        return await downloadTrack({
          track: track.FALLBACK,
          quality,
          info,
          path,
          totalTracks,
          trackNumber,
          fallbackTrack: false,
          isFallback: true,
          message,
        });
      }
      logUpdate(signale.warn(`Skipped "${track.SNG_TITLE}", track not available.`));
      logUpdate.done();
      return;
    }

    const headers: {[key: string]: string} = {};
    const tmpfile = `d-fi_${quality}_${track.SNG_ID}_${simulate ? 'simulate' : track.MD5_ORIGIN}`;
    if (simulate) {
      coverSize = 56;
      headers.range = 'bytes=0-1023';
    } else if (existsSync(tmpfile)) {
      const tmpfilestat = statSync(tmpfile);
      downloaded = tmpfilestat.size;
      headers.range = 'bytes=' + tmpfilestat.size + '-';
    }

    const bar = progressBar(fileSize, 40);
    const humanSizeTotal = (fileSize / 1024 / 1024).toFixed(2);
    let transferredLast = downloaded;
    await pipeline(
      got.stream(url, {responseType: 'buffer', headers}).on('downloadProgress', ({transferred}) => {
        // Report download progress
        transferred += downloaded;
        if (transferred - transferredLast > 50000) {
          transferredLast = transferred;
          logUpdate(
            signale.info(`Downloading ${track.SNG_TITLE} ${message}\n  ${bar(transferred)} | ${humanSizeTotal}MiB`),
          );
        }
      }),
      createWriteStream(tmpfile, {flags: 'a', autoClose: true}),
    );

    logUpdate(signale.pending('Decrypting ' + track.SNG_TITLE + ' by ' + track.ART_NAME));
    const decryptedTrack = decryptDownload(readFileSync(tmpfile), track.SNG_ID);

    logUpdate(signale.pending('Tagging ' + track.SNG_TITLE + ' by ' + track.ART_NAME));
    const trackWithMetadata = await addTrackTags(decryptedTrack, track, coverSize);

    // Delete temporary file now
    unlinkSync(tmpfile);

    logUpdate(signale.pending('Saving ' + track.SNG_TITLE + ' by ' + track.ART_NAME));
    if (!simulate) {
      // Create directory if not exists
      const dir = dirname(savePath);
      if (!existsSync(dir)) {
        mkdirSync(dir, {recursive: true});
      }
      // Save file to disk
      writeFileSync(savePath, trackWithMetadata);
    }

    // Print sucess info
    logUpdate(
      signale.success(`${isFallback ? chalk.yellow('[Fallback] ') : ''}${track.SNG_TITLE} by ${track.ART_NAME}`),
    );
    logUpdate.done();

    return savePath;
  } catch (err) {
    logUpdate(signale.error(track.SNG_TITLE));
    logUpdate.done();
    logUpdate(signale.note(err.message));
    logUpdate.done();
  }
};

export default downloadTrack;
